

#include <stdio.h>


#include <stdio.h>
#include <string.h>
#include <list>
#include <gio/gio.h>
#include <glib.h>
#include <netinet/in.h>
#include <unistd.h>


#include "agent.h"
#include "xm_log.h"
#include "xm_crash.h"
#include "xm_version.h"
#include "xm_app_config.h"

#define conn_dbgv(FMT, ARGS...) dbgv("|%s| " FMT, conn->obj_id, ##ARGS )
#define conn_dbgi(FMT, ARGS...) dbgi("|%s| " FMT, conn->obj_id, ##ARGS )
#define conn_dbge(FMT, ARGS...) dbge("|%s| " FMT, conn->obj_id, ##ARGS )

typedef struct relay_conn_st * relay_conn_t;

typedef union {
    struct sockaddr_storage storage;
    struct sockaddr addr;
} xaddr;

typedef struct {
    int is_allow_change_ip;
    xaddr addr_src;
    xaddr addr_dst;
    int recv_first;
    int packet_count;
}xpeer;

struct relay_conn_st{
    char obj_id[32];
    GSocket * gsock;
    int fd;
    guint source;
    xpeer peer1;
    xpeer peer2;
    unsigned char buff[2*1024];
};


static
void dump_config(app_config * config){
    gdbgi("  opt: log-config-file=[%s]", config->log_config_file_name);
    gdbgi("  opt: media-ip=[%s]", config->media_ip);
    gdbgi("  opt: min-port=[%d]", config->min_port);
    gdbgi("  opt: max-port=[%d]", config->max_port);
    gdbgi("  opt: http-ip=[%s]", config->http_ip);
    gdbgi("  opt: http-port=[%d]", config->http_port);
    gdbgi("  opt: tlv-path=[%s]", config->tlv_path);
    gdbgi("  opt: job-path=[%s]", config->job_path);
    gdbgi("  opt: ip1-src=[%s]", config->ip1_src);
    gdbgi("  opt: ip1-dst=[%s]", config->ip1_dst);
    gdbgi("  opt: ip2-src=[%s]", config->ip2_src);
    gdbgi("  opt: ip2-dst=[%s]", config->ip2_dst);
    gdbgi("  opt: echo=[%d]", config->echo);
}

static inline
int init_socket_addrin(const char * ip, int port, struct sockaddr_in * addr){
    memset(addr, 0, sizeof(struct sockaddr_in));
    addr->sin_family = AF_INET;
    addr->sin_port = htons(port);
    if(ip && ip[0] != '\0'){
        addr->sin_addr.s_addr = inet_addr(ip);
    }else{
        addr->sin_addr.s_addr = htonl(INADDR_ANY);
    }
    return 0;
}

static inline
int sock_addr_is_valid(struct sockaddr_in * src){
    return (src->sin_family > 0
            && src->sin_addr.s_addr > 0
            && src->sin_port > 0);
}

static inline
int sock_addr_is_equ(struct sockaddr_in * src, struct sockaddr_in * dst){
    return (src->sin_family == dst->sin_family
            && src->sin_addr.s_addr == dst->sin_addr.s_addr
            && src->sin_port == dst->sin_port);
}



static inline
int xpeer_init_addr(xpeer * peer, const char * src_ip, int src_port, const char * dst_ip, int dst_port){
    memset(peer, 0, sizeof(xpeer));
    // 如果没设置--ip1-dst，在这里设置
    if(!dst_ip || dst_ip[0] == '\0'){
        dst_ip = src_ip;
        dst_port = src_port;
    }
    
    init_socket_addrin(src_ip, src_port,  (struct sockaddr_in *)&peer->addr_src.addr);
    init_socket_addrin(dst_ip, dst_port,  (struct sockaddr_in *)&peer->addr_dst.addr);
    return 0;
}

static inline
void native_addr_to_string (struct sockaddr * addr, char *dst){
    
    NiceAddress naddr;
    nice_address_set_from_sockaddr (&naddr, addr);
    nice_address_to_string (&naddr, dst);
    
    sprintf(dst+strlen(dst), ":%d", nice_address_get_port(&naddr));
}

static inline
gboolean xglib_address_to_string (GSocketAddress * gaddr, gchar *dst){
    union {
        struct sockaddr_storage storage;
        struct sockaddr addr;
    } sa;
    g_socket_address_to_native (gaddr, &sa.addr, sizeof (sa), NULL);
    
//    NiceAddress naddr;
//    nice_address_set_from_sockaddr (&naddr, &sa.addr);
//    nice_address_to_string (&naddr, dst);
//    sprintf(dst+strlen(dst), ":%d", nice_address_get_port(&naddr));
    
    native_addr_to_string(&sa.addr, dst);
    
    return TRUE;
}

static inline
bool check_set_peer_addr(relay_conn_t conn, xpeer * peer, struct sockaddr_in * from , const char * direction){
    struct sockaddr_in * src_addr = (struct sockaddr_in *)&peer->addr_src.addr;
    
    if(src_addr->sin_addr.s_addr > 0){
        if(src_addr->sin_addr.s_addr != from->sin_addr.s_addr){
            return false;
        }
    }
    
    if(src_addr->sin_port > 0){
        if(src_addr->sin_port != from->sin_port){
            return false;
        }
    }
    
    if(!sock_addr_is_valid(src_addr) || !peer->recv_first){
        peer->recv_first = 1;
        *src_addr = *from;
        const char *from_ip = inet_ntoa(from->sin_addr);
        int from_port = ntohs(from->sin_port);
        conn_dbgi("got first packet from %s, [%s:%d]", direction, from_ip, from_port);
        if(!sock_addr_is_valid((struct sockaddr_in *)&peer->addr_dst.addr)){
            peer->addr_dst = peer->addr_src;
            conn_dbgi("set dst to %s, [%s:%d]", direction, from_ip, from_port);
        }
    }
    
    return true;
}

static inline
void send_directly(relay_conn_t conn, xpeer * peer, unsigned char * data, int data_len){
    // TODO: check dst addr valid
    int ret;
    ret = sendto(conn->fd, data, data_len, 0, (struct sockaddr*)&peer->addr_dst.addr, sizeof(peer->addr_dst.addr));
    peer->packet_count++;
}

static inline
void process_udp(relay_conn_t conn, unsigned char * data, int data_length, struct sockaddr_in * from_addr){
    if(check_set_peer_addr(conn, &conn->peer1, from_addr, "peer1")){
        if(!sock_addr_is_valid((struct sockaddr_in *)&conn->peer2.addr_dst.addr)){
            // unknown peer2 addr
            return ;
        }
        send_directly(conn, &conn->peer2, data, data_length);
        return ;
    }
    
    if(check_set_peer_addr(conn, &conn->peer2, from_addr, "peer2")){
        if(!sock_addr_is_valid((struct sockaddr_in *)&conn->peer1.addr_dst.addr)){
            // unknown peer1 addr
            return ;
        }
        send_directly(conn, &conn->peer1, data, data_length);
        return ;
    }
    
    if(conn->peer2.is_allow_change_ip){
        xpeer * peer2 = &conn->peer2;
        peer2->addr_src.addr = *(struct sockaddr *)from_addr;
        peer2->addr_dst = peer2->addr_src;
        const char *from_ip = inet_ntoa(from_addr->sin_addr);
        int from_port = ntohs(from_addr->sin_port);
        conn_dbgi("peer2 change to [%s:%d]", from_ip, from_port);
        send_directly(conn, &conn->peer1, data, data_length);
        return;
    }
    
    // never reach here
    const char *from_ip = inet_ntoa(from_addr->sin_addr);
    int from_port = ntohs(from_addr->sin_port);
    conn_dbge("unexpect reach process_udp end !!!, from [%s:%d]", from_ip, from_port);
    
}

static
gboolean udp_gio_in (GIOChannel *source, GIOCondition cond, gpointer data)
{
    relay_conn_t conn = (relay_conn_t) data;
    app_config * config = app_config_get();
    
//    GSocket *sock = conn->gsock;
//    GError *error = NULL;
//    gchar buf[1600];
//    GSocketAddress * addr = NULL;
//    gssize len = g_socket_receive_from (sock,
//                                        &addr, //GSocketAddress **address,
//                                        buf,
//                                        1600,
//                                        NULL,
//                                        &error);
//    if(len <= 0){
//        conn_dbgi ("recv error: %ld\n", len);
//        return TRUE;
//    }
//    
//    gchar tmpbuf[INET6_ADDRSTRLEN];
//    xglib_address_to_string (addr, tmpbuf);
//    conn_dbgi ("recv from %s, %ld bytes", tmpbuf, len);
//    if(config->echo){
//        GError *err = NULL;
//        int sent = g_socket_send_to(sock, addr, buf, len, NULL, &err);
//        return TRUE;
//    }
    
    struct sockaddr_in tempadd;
    socklen_t len = sizeof(tempadd);
    int bytes_to_recv = sizeof(conn->buff);
    
    while(1){
        int sock_ret = recvfrom(conn->fd, conn->buff, bytes_to_recv, 0, (struct sockaddr*)&tempadd, &len);
        if(sock_ret <= 0){
            return TRUE;
        }
        int udp_len = sock_ret;
        
        gchar addr_str[INET6_ADDRSTRLEN];
        native_addr_to_string((struct sockaddr *) &tempadd, addr_str);
        conn_dbgv ("recv from %s, %d bytes", addr_str, udp_len);
        
        if(config->echo){
            sock_ret = sendto(conn->fd, conn->buff, udp_len, 0, (struct sockaddr*)&tempadd, sizeof(tempadd));
        }else{
            process_udp(conn, conn->buff, udp_len, &tempadd);
        }
        
    }
    
    
    return TRUE;
}


int main (int argc, char **argv){
    
    xm_crash_init(argc, argv);
    
    if(app_config_parse(argc, argv)){
        return 1;
    }
    
    app_config * config = app_config_get();
    xm_log_init(config->log_config_file_name);
    
    gdbgi("APP_VER = %s-%s", APP_VER_STR, GIT_DESCRIBE_STR);
    //gdbgi("GIT_VER = %s", GIT_DESCRIBE_STR);
    
    if(config->http_port == 0){
        config->http_port = 1880;
    }
    
    if(config->min_port == 0){
        config->min_port = 50000;
        config->max_port = 50005;
    }
    
    // TODO: remove me!!!
//    config->ip1_src = "121.41.87.159";
//    config->min_port = 61000;
//    config->max_port = 61005;
    
    dump_config(config);
    
    for(int i = config->min_port; i <= config->max_port; i++){
        int port = i;
        relay_conn_t conn = (relay_conn_t)g_malloc0(sizeof(struct relay_conn_st));
        sprintf(conn->obj_id, "port-%d", port);
        
        
        xpeer_init_addr(&conn->peer1, config->ip1_src, port, config->ip1_dst, port);
        xpeer_init_addr(&conn->peer2, config->ip2_src, 0, config->ip2_dst, 0);
        conn->peer2.is_allow_change_ip = 1;
        
        
        GSocketAddress * gsockAddr = G_SOCKET_ADDRESS(g_inet_socket_address_new(g_inet_address_new_any(G_SOCKET_FAMILY_IPV4), port));
        GError *err = NULL;
        conn->gsock = g_socket_new(G_SOCKET_FAMILY_IPV4,
                             G_SOCKET_TYPE_DATAGRAM,
                             G_SOCKET_PROTOCOL_UDP,
                             &err);
        if (err != nullptr) {
            g_warning("[PID %d] g_socket_new(port:%d): error: %s", getpid(), port, err->message);
            exit(-1);
        }

        g_assert(err == NULL);
        if (conn->gsock == NULL) {
            gdbge("fail to new udp socket for port %d", port);
            exit(1);
        }
        
//        g_socket_set_blocking (conn->gsock, FALSE); // TODO: enable non-blocking
        if (g_socket_bind (conn->gsock, gsockAddr, TRUE, NULL) == FALSE){
            gdbge("fail to bind udp socket for port %d", port);
            exit(1);
            
        }
        
        conn->fd = g_socket_get_fd(conn->gsock);
        GIOChannel* channel = g_io_channel_unix_new(conn->fd);
        conn->source = g_io_add_watch(channel, G_IO_IN,
                                      (GIOFunc) udp_gio_in, conn);
        g_io_channel_unref(channel);
        
        
        
    }
    
    GMainLoop * gloop = g_main_loop_new(NULL, FALSE);
    g_main_loop_run(gloop);
    
    
    return 0;
}
